﻿//-----------------------------------------------------------------------
// <copyright company="Microsoft">
//      (c) Copyright Microsoft Corporation.
//      This source is subject to the Microsoft Public License (Ms-PL).
//      Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
//      All other rights reserved.
// </copyright>
//-----------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using HireMyStuffSilverlight.helpers;

namespace Insight.Library.Navigation
{
    /// <summary>
    /// Defines a URI to URI mapping.
    /// </summary>
    /// <QualityBand>Preview</QualityBand>
    public sealed class FragmentUriMapping:CustomUriMapping
    {
        #region Fields

        private Uri _uri;
        private Uri _mappedUri;
        private Regex _uriRegex;
        private bool _uriRegexIdentifierUsedTwice;
        private bool _uriHasQueryString;
        private bool _uriHasFragment;
        private List<string> _uriIdentifiers;
        private List<string> _mappedUriIdentifiers;
        private static readonly Regex _conversionRegex = new Regex("(?<ConversionCapture>{.*?})", RegexOptions.ExplicitCapture);
        private bool _initialized;

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the original URI value or pattern.
        /// </summary>
        public Uri Uri
        {
            get
            {
                return _uri;
            }

            set
            {
                _uri = value;
                _initialized = false;
            }
        }

        /// <summary>
        /// Gets or sets the mapped URI value or pattern.
        /// </summary>
        public Uri MappedUri
        {
            get
            {
                return _mappedUri;
            }

            set
            {
                _mappedUri = value;
                _initialized = false;
            }
        }

        #endregion Properties

        #region Methods

        private bool UriTemplateContainsSameIdentifierTwice(Uri uri, out Regex uriRegex)
        {
            if (uri == null)
            {
                uriRegex = null;
                return false;
            }

            string origString = uri.OriginalString;
            MatchCollection matches = _conversionRegex.Matches(origString);
            _uriIdentifiers = new List<string>();

            foreach (Match m in matches)
            {
                string valWithoutBraces = m.Value.Replace("{", String.Empty).Replace("}", String.Empty);

                // We've hit the same identifier being used twice.  This isn't valid, so return false.
                if (_uriIdentifiers.Contains(valWithoutBraces))
                {
                    uriRegex = null;
                    return true;
                }

                _uriIdentifiers.Add(valWithoutBraces);
            }

            string convertedValue = _conversionRegex.Replace(origString, "(?<$1>.*?)").Replace("{", String.Empty).Replace("}", String.Empty);
            uriRegex = new Regex("^" + convertedValue + "$");
            return false;
        }

        private void GetIdentifiersForMappedUri(Uri mappedUri)
        {
            string origString = mappedUri.OriginalString;
            MatchCollection matches = _conversionRegex.Matches(origString);
            _mappedUriIdentifiers = new List<string>();

            foreach (Match m in matches)
            {
                string valWithoutBraces = m.Value.Replace("{", String.Empty).Replace("}", String.Empty);
                if (!_mappedUriIdentifiers.Contains(valWithoutBraces))
                {
                    _mappedUriIdentifiers.Add(valWithoutBraces);
                }
            }
        }

        private void Initialize()
        {
            // Initialize stuff for the Uri template
            Regex newFromRegex = null;
            _uriRegexIdentifierUsedTwice = UriTemplateContainsSameIdentifierTwice(_uri, out newFromRegex);
            _uriHasQueryString = !String.IsNullOrEmpty(UriParsingHelper.InternalUriGetQueryString(_uri));
            _uriHasFragment = !String.IsNullOrEmpty(UriParsingHelper.InternalUriGetFragment(_uri));
            _uriRegex = newFromRegex;
            
            // Initialize stuff for the mapped Uri template
            GetIdentifiersForMappedUri(_mappedUri);

            _initialized = true;
        }

        /// <summary>
        /// Attempts to process a Uri, if it matches the Uri template
        /// </summary>
        /// <param name="uri">The Uri to map</param>
        /// <returns>The Uri after mapping, or null if mapping did not succeed</returns>
        public Uri MapUri(Uri uri)
        {
            CheckPreconditions();

            string uriAsString = uri.ToString();

            Match m = _uriRegex.Match(uri.ToString());

            if (!m.Success)
            {
                return null;
            }

            string uriAfterMappingBase = UriParsingHelper.InternalUriGetBaseValue(_mappedUri)+"?"+UriParsingHelper.InternalUriGetQueryString(_mappedUri);
            IDictionary<string, string> uriAfterMappingFragments = FragmentParsingHelper.ParseFragmentToDictionary(UriParsingHelper.InternalUriGetFragment(_mappedUri));
            IDictionary<string, string> originalFragmentsString = FragmentParsingHelper.ParseFragmentToDictionary(UriParsingHelper.InternalUriGetFragment(uri));

            // 'uriValues' is the values of the identifiers from the 'Uri' template, as they appear in the Uri
            // being processed
            IDictionary<string, string> uriValues = new Dictionary<string, string>();

            // i begins at 1 because the group at index 0 is always equal to the parent's Match,
            // which we do not want.  We only want explicitly-named groups.
            int groupCount = m.Groups.Count;
            for (int i = 1; i < groupCount; i++)
            {
                uriValues.Add(_uriRegex.GroupNameFromNumber(i), m.Groups[i].Value);
            }

            foreach (string identifier in _mappedUriIdentifiers)
            {
                string identifierWithBraces = "{" + identifier + "}";
                string replacementValue = (uriValues.ContainsKey(identifier) ? uriValues[identifier] : String.Empty);

                // First check for identifiers in the base Uri, and replace them as appropriate
                uriAfterMappingBase = uriAfterMappingBase.Replace(identifierWithBraces, replacementValue);

                // Then, look through the query string in the framents (both the key and the value) and replace as appropriate
                string[] keys = new string[uriAfterMappingFragments.Keys.Count];
                uriAfterMappingFragments.Keys.CopyTo(keys, 0);
                foreach (string key in keys)
                {
                    // First check if the value contains it, as this is an easy replacement
                    if (uriAfterMappingFragments[key].Contains(identifierWithBraces))
                    {
                        if (uriValues.ContainsKey(identifier))
                        {
                            uriAfterMappingFragments[key] = uriAfterMappingFragments[key].Replace(identifierWithBraces, replacementValue);
                        }
                    }

                    // If the key itself contains the identifier, then we need to remove the existing item with the key that
                    // contains the identifier, and re-add to the dictionary with the new key and the pre-existing value
                    if (key.Contains(identifierWithBraces))
                    {
                        string existingVal = uriAfterMappingFragments[key];
                        uriAfterMappingFragments.Remove(key);
                        uriAfterMappingFragments.Add(key.Replace(identifierWithBraces, replacementValue), existingVal);
                    }
                }
            }

            foreach (string key in originalFragmentsString.Keys)
            {
                if (!uriAfterMappingFragments.ContainsKey(key))
                {
                    uriAfterMappingFragments.Add(key, originalFragmentsString[key]);
                }
                else
                {
                    // If a value is present in the originally-navigated-to query string, it
                    // takes precedence over anything in the aliased query string by default.
                    uriAfterMappingFragments[key] = originalFragmentsString[key];
                }
            }

            return UriParsingHelper.InternalUriCreateWithQueryStringValues(uriAfterMappingBase, new Dictionary<string, string>(), FragmentParsingHelper.ConvertToString(uriAfterMappingFragments));
        }

        private void CheckPreconditions()
        {
            if (_uri == null)
            {
                throw new InvalidOperationException("UriMapping_UriTemplateMustBeSpecified");
            }

            if (_mappedUri == null)
            {
                throw new InvalidOperationException("UriMapping_MappedUriTemplateMustBeSpecified");
            }

            if (_initialized == false)
            {
                Initialize();
            }

            if (_uriHasQueryString)
            {
                throw new InvalidOperationException("UriMapping_UriTemplateCannotHaveAQueryString");
            }

            if (_uriHasFragment)
            {
                throw new InvalidOperationException("UriMapping_UriTemplateCannotHaveAFragment");
            }

            if (_uriRegexIdentifierUsedTwice)
            {
                throw new InvalidOperationException("UriMapping_UriTemplateCannotContainTheSameIdentifierMoreThanOnce");
            }
        }

        #endregion

        public override bool MapUri(Uri unmappedUri, out Uri mappedUri)
        {
            mappedUri = MapUri(unmappedUri);
            return mappedUri!=null?true:false;
        }
    }
}
